# -*- coding:utf-8 -*-
__author__ = 'xigu007'
__all__ = ('PullDrawerView', )
from  kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.metrics import sp
from kivy.clock import Clock
from kivy.properties import BooleanProperty,NumericProperty,StringProperty
from kivy.effects.scroll import ScrollEffect
from kivy.uix.stencilview import StencilView
from kivy.effects.dampedscroll import DampedScrollEffect
from recycleview.recycleview import _kivy_1_9_1, RecycleView
from recycleview.staggered_layout import StaggeredViewMixin, StaggeredLayoutManager
from functools import partial

class PullDrawerEffect(ScrollEffect):
    edge_damping = NumericProperty(0.2)  #0.4
    spring_constant = NumericProperty(8.0) #5.0
    min_overscroll = NumericProperty(.3)
    round_value = BooleanProperty(True)

    #上啦或下拉过线，状态改变
    above = BooleanProperty(False)

    #下拉panel的高度
    top_panel_height = 0

    #上啦panel的高度
    bottom_panel_height = 0

    #正在刷新
    _docked = False

    #
    _to_be_docked = False

    _active_panel = ''
    _panel_height = 0

    def __init__(self, **kwargs):
        self.register_event_type('on_refresh')
        super(PullDrawerEffect,self).__init__(**kwargs)

    def on_refresh(self, *args):
        pass

    def finish_refresh(self):
        self._to_be_docked = False
        self._docked = False
        self.trigger_velocity_update()

    def set_top_panel_height(self,h):
        self.top_panel_height = h

    def set_bottom_panel_height(self,h):
        self.bottom_panel_height = h

    def on_overscroll(self, *args):
        #print 'on_overscroll={}'.format(self.overscroll)
        self._update_pull_state()
        self.trigger_velocity_update()

    def apply_distance(self, distance):
        overscroll = self.overscroll
        if self._to_be_docked:
            overscroll =abs(overscroll)  - self._panel_height
        os = abs(overscroll)
        if os:
            distance /= 1. + os / sp(200.)
        super(PullDrawerEffect, self).apply_distance(distance)

    def on_value(self, *args):
        scroll_min = self.min
        scroll_max = self.max
        if scroll_min > scroll_max:
            scroll_min, scroll_max = scroll_max, scroll_min
        if self.value < scroll_min:
            if not self.is_manual:
                if 0 == self.overscroll:
                    self.reset(scroll_min)
            self.overscroll = self.value - scroll_min
        elif self.value > scroll_max:
            if not self.is_manual:
                if 0 == self.overscroll:
                    self.reset(scroll_max)
            self.overscroll = self.value - scroll_max
        else:
            self.overscroll = 0
        self.scroll = self.value

    def _update_pull_state(self):
        if self._to_be_docked:
            return
        if self.overscroll < 0.0:
            if self.top_panel_height > 0:
                self._active_panel = 'top'
                self._panel_height = self.top_panel_height
                self.above = True if abs(self.overscroll)  - self._panel_height > 0 else False
        else:
            if self.bottom_panel_height > 0:
                self._active_panel = 'bottom'
                self._panel_height = self.bottom_panel_height
                self.above = True if abs(self.overscroll)  - self._panel_height > 0 else False

    def stop(self, val, t=None):
        super(PullDrawerEffect, self).stop(val, t)
        if not self._docked:
            self._to_be_docked = True if self.above else False

    def update_velocity(self, dt):
        min_value = self.min
        max_value = self.max
        overscroll = self.overscroll
        if self._to_be_docked and overscroll != 0:
            active_panel = self._active_panel
            panel_height = self._panel_height
            if active_panel.startswith('t'):
                overscroll =overscroll + panel_height
                min_value = self.min - panel_height
            else:
                overscroll =overscroll - panel_height
                max_value = self.max + panel_height
        #print 'velocity={} overscroll={}'.format(self.velocity, overscroll)
        if abs(self.velocity) <= self.min_velocity and abs(overscroll) <= 1.0:
            self.velocity = 0
            if self.round_value:
                self.value = round(self.value)
            if self._to_be_docked and (not self.is_manual) and (not self._docked):
                self._docked = True
                self.dispatch('on_refresh', self._active_panel)
                self.above = False
            return

        total_force = self.velocity * self.friction
        if abs(overscroll) > self.min_overscroll:
            total_force += self.velocity * self.edge_damping
            total_force += overscroll * self.spring_constant
        else:
            if self._to_be_docked and self.overscroll != 0:
                self.overscroll = panel_height if self._active_panel.startswith('b') else -panel_height
            else:
                self.overscroll = 0

        stop_overscroll = ''
        if not self.is_manual:
            if self.overscroll > 0 and self.velocity < 0:
                stop_overscroll = 'max'
            elif self.overscroll < 0 and self.velocity > 0:
                stop_overscroll = 'min'

        self.velocity = self.velocity - total_force
        if not self.is_manual:
            self.apply_distance(self.velocity * dt)
            if stop_overscroll == 'min' and self.value > min_value:
                self.value = min_value
                self.velocity = 0
                return
            if stop_overscroll == 'max' and self.value < max_value:
                self.value = max_value
                self.velocity = 0
                return
        self.trigger_velocity_update()

class PullPanel(object):
    docked = BooleanProperty(False)
    pull_height = NumericProperty(0)
    def set_pull_state(self, state):
        pass

class DefaultPanel(PullPanel, Label):
    text1 = StringProperty('')
    def __init__(self,**kwargs):
        self.text1 = '继续下拉..'
        super(DefaultPanel, self).__init__(**kwargs)
        self.size_hint_y = None
        self.height = dp(94)

    def on_docked(self, instance, value):
        self.stop_animate_all()
        if value:
            self.text1 = '正在更新...'
        else:
            self.text1 = '更新完成'

    def set_pull_state(self,state):
        if self.docked:
            return
        if state == 'above':
            self.text1 = '松开后更新..'
        else:
            self.text1 = '继续下拉..'

class PullDrawerView(StencilView):
    bottom_enable = BooleanProperty(False)
    #上拉下拉高度
    pull_height = 0
    _scroll_view = None
    _top_panel = None
    _bottom_panel = None
    _active_panel = None
    _refresh_time_out = 10
    def __init__(self, **kwargs):
        self.register_event_type('on_begin_loading')
        self.register_event_type('on_end_loading')
        super(PullDrawerView, self).__init__(**kwargs)
        if self._scroll_view is None:
            self.scroll_view = RecycleView()

    def _scrolling(self, instance, value):
        pass

    def on_pos(self,instance,value):
        sv = self._scroll_view
        if sv:
            sv.pos = value
        mp = self._active_panel
        if mp:
            if mp is self._top_panel:
                self.reset_panel('top')
            else:
                self.reset_panel('bottom')

    def on_size(self,instance,value):
        sv = self._scroll_view
        if sv:
            sv.size = value
        self.reset_panel('top')
        self.reset_panel('bottom')

    def set_refresh_timeout(self, value):
        self._refresh_time_out = value

    def reset_panel(self, panel):
        if panel.startswith('t'):
            mp = self._top_panel
            if mp:
                mp.x = self.x
                mp.y = self.top - self.pull_height
                mp.width =self.width
        else:
            mp = self._bottom_panel
            if mp:
                mp.x = self.x
                mp.y = self.y - self.pull_height - mp.height
                mp.width =self.width

    def _panel_pull_state(self, instance, value):
        mp = self._active_panel
        if mp:
            if value:
                #松开后加载
                mp.set_pull_state('above')
            else:
                #继续下拉
                mp.set_pull_state('below')

    def _pull_height(self, instance, value):
        self.pull_height = abs(value)
        if value < 0.0:
            #正在下拉
            self._active_panel = self._top_panel
            if self._active_panel:
                self._active_panel.pos = (self.x, self.y + self.height-self.pull_height)
        elif value > 0.0:
            #正在上拉
            self._active_panel = self._bottom_panel
            if self._active_panel:
                self._active_panel.pos = (self.x, self.y + self.pull_height - self._active_panel.height)
        else:
            panel = self._top_panel
            if panel:
                panel.pull_height = 0
                panel.pos = (self.x, self.y + self.height)
            panel = self._bottom_panel
            if panel:
                panel.pull_height = 0
                panel.pos = (self.x, self.y - panel.height)

    def _bind_panel_height(self, panel, instance, value):
        sv = self._scroll_view
        if sv:
            effect_y = sv.effect_y
            if effect_y:
                if panel.startswith('t'):
                    effect_y.set_top_panel_height(value)
                else:
                    effect_y.set_bottom_panel_height(value)
    @property
    def scroll_view(self):
        return  self._scroll_view
    @scroll_view.setter
    def scroll_view(self, value):
        sv = self._scroll_view
        if value is sv:
            return
        if not value:
            return
        if sv is not None:
            effect_y = sv.effect_y
            funbind = effect_y.funbind if _kivy_1_9_1 else effect_y.fast_unbind
            funbind('above',self._panel_pull_state)
            funbind('overscroll', self._pull_height)
            funbind('on_refresh', self._panel_refresh)
            funbind('scroll', self._scrolling)
            self.remove_widget(sv)
        sv = self._scroll_view = value
        sv.do_scroll_x=False
        sv.layout_manager = StaggeredLayoutManager()
        sv.key_viewclass = "viewclass"
        sv.size_hint = (None,None)
        effect_y = sv.effect_y = PullDrawerEffect()
        fbind = effect_y.fbind if _kivy_1_9_1 else effect_y.fast_bind
        fbind('above',self._panel_pull_state)
        fbind('overscroll', self._pull_height)
        fbind('on_refresh', self._panel_refresh)
        fbind('scroll', self._scrolling)
        self.add_widget(sv)

    @property
    def top_panel(self):
        return self._top_panel
    @top_panel.setter
    def top_panel(self, value):
        mp = self._top_panel
        if value is mp:
            return
        if not value:
            self._bind_panel_height('top', mp, 0)
            return
        if mp is not None:
            self.remove_widget(mp)
            funbind = mp.funbind if _kivy_1_9_1 else mp.fast_unbind
            funbind('height',partial(self._bind_panel_height,'top'))
        mp = self._top_panel = value
        fbind = mp.fbind if _kivy_1_9_1 else mp.fast_bind
        fbind('height',partial(self._bind_panel_height,'top'))
        self.reset_panel('top')
        self.add_widget(mp)
        self._bind_panel_height('top', mp, mp.height)

    @property
    def bottom_panel(self):
        return self._bottom_panel
    @bottom_panel.setter
    def bottom_panel(self, value):
        mp = self._bottom_panel
        if value is mp:
            return
        if not value:
            self._bind_panel_height('bottom', mp, 0)
            return
        if mp is not None:
            self.remove_widget(mp)
            funbind = mp.funbind if _kivy_1_9_1 else mp.fast_unbind
            funbind('height',partial(self._bind_panel_height,'bottom'))
        mp = self._bottom_panel = value
        fbind = mp.fbind if _kivy_1_9_1 else mp.fast_bind
        fbind('height',partial(self._bind_panel_height,'bottom'))
        self.reset_panel('bottom')
        self.add_widget(mp)
        self._bind_panel_height('bottom',mp, mp.height)

    def _do_refresh_time_out(self,dt):
        self.dispatch('on_end_loading','top')
        self.dispatch('on_end_loading','bottom')

    def _panel_refresh(self, *args):
        panel = args[1]
        if panel.startswith('t'):
            mp = self._top_panel
        else:
            mp = self._bottom_panel
        if mp:
            mp.docked = True
            Clock.unschedule(self._do_refresh_time_out)
            Clock.schedule_once(self._do_refresh_time_out, self._refresh_time_out)
            if self.dispatch('on_begin_loading', panel):
                self.dispatch('on_end_loading',panel)

    def on_begin_loading(self, *args):
        return False

    def on_end_loading(self, *args):
        panel = args[0]
        if panel.startswith('t'):
            mp = self._top_panel
        else:
            mp = self._bottom_panel
        if mp:
            Clock.unschedule(self._do_refresh_time_out)
            mp.docked = False
            sv = self._scroll_view
            effect_y = sv.effect_y
            effect_y.finish_refresh()

if __name__ == '__main__':
    from kivy.app import App
    from kivy.uix.gridlayout import GridLayout
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.label import Label
    from kivy.uix.button import Button
    from kivy.effects.kinetic import KineticEffect
    from kivy.effects.scroll import ScrollEffect
    from kivy.effects.dampedscroll import DampedScrollEffect
    from kivy.metrics import dp
    from util_widget.pullpanel import PullRefresh
    KineticEffect.speed_scale = 1.3
    ScrollEffect.drag_threshold = dp(1.5)
    #ScrollEffect.friction = 0.06
    ScrollEffect.min_distance = 1.5
    ScrollEffect.min_velocity = 2.0
    import os
    os.environ['KIVY_VALID_FONT'] = 'DroidSansFallback.ttf'
    kv_str = '''
BoxLayout:
    pull_view: pull_view
    canvas:
        Color:
            rgb: .92,.92,.92
        Rectangle:
            pos: self.pos
            size: self.size
    orientation: "vertical"
    PullDrawerView:
        id: pull_view
    BoxLayout:
        size_hint_y:None
        height:50
        Button:
            text:'generate_data'
            on_release: app.generate_data()
        Button:
            text:'finish refresh'
            on_release: app.finish_refresh()
    '''
    class MyStaggeredView(Button, StaggeredViewMixin):
        s_height = [140, 75, 150, 55, 200, 120, 170, 250, 300]
        def compute_height(self, recycleview, index, view_width):
            self.height = self.s_height[index%9]
            #self.height = 0.5*(self.s_height[index%9] + view_width)
            self.text = 'buttom{}  {}'.format(index, self.height)
            return self.height

    class TestApp(App):
        def build(self):
            # create a default grid layout with custom width/height
            self.root = Builder.load_string(kv_str)
            #self.root = DefaultPanel()
            return self.root

        def finish_refresh(self):
            pull_view = self.root.pull_view
            pull_view.dispatch('on_end_loading', 'top')

        def generate_data(self):
            if self.root:
                contacts = []
                pull_view = self.root.pull_view
                pull_view.top_panel = PullRefresh()
                rv = pull_view.scroll_view
                for x in range(14):
                    contacts.append({
                        "index": x,
                        "viewclass": "MyStaggeredView",
                        "text": "button{}".format(x)
                    })
                rv.data = contacts

    TestApp().run()


